﻿/**
 * Copyright (C) 2011 original author and authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NGuice.Inject.Spi;
using NGuice.Inject.Internal.Util;

namespace NGuice.Inject.Internal
{
    //complete！
    /// <summary>
    /// 注入给定类型的实例成员
    /// </summary>
    internal sealed class MembersInjectorImpl<T> : IMembersInjector<T>
    {
        //private final TypeLiteral<T> typeLiteral;
        private readonly InjectorImpl injector;

        private readonly ImmutableList<SingleMemberInjector> memberInjectors;
        //通常认为一个注入器包含的可注入成员是泛型参数T的子类，但事实并非如此。这个问题和依赖倒置一样，
        //可注入器包含的成员注入列表，是泛型T参数的超类！因为只有超类成员，才允许子类T的注入赋值！
        private readonly ImmutableList<IMembersInjector> userMembersInjectors;//成员必须是IMembersInjector<? super T>即泛型参数必须是T的超类类型
        private readonly ImmutableList<InjectionListener> injectionListeners;//成员必须是IMembersInjector<? super T>即泛型参数必须是T的超类类型
        /*if[AOP]*/
        private readonly ImmutableList<MethodAspect> addedAspects;
        /*end[AOP]*/

        public MembersInjectorImpl(InjectorImpl injector, EncounterImpl<T> encounter, ImmutableList<SingleMemberInjector> memberInjectors)
        {
            this.injector = injector;
            //this.typeLiteral = typeLiteral;
            this.memberInjectors = memberInjectors;
            this.userMembersInjectors = encounter.MembersInjectors;
            this.injectionListeners = encounter.InjectionListeners;
            /*if[AOP]*/
            this.addedAspects = encounter.Aspects;
            /*end[AOP]*/
        }

        public ImmutableList<SingleMemberInjector> MemberInjectors
        {
            get
            {
                return memberInjectors;
            }
        }

        public void InjectMembers(T instance)
        {
            Errors errors = new Errors(typeof(T));
            try
            {
                InjectAndNotify(instance, errors, false);
            }
            catch (ErrorsException e)
            {
                errors.Merge(e.Errors);
            }

            errors.ThrowProvisionExceptionIfErrorsExist();
        }

        class Context : ContextualCallable<AsyncVoid>
        {
            private T instance;
            private Errors errors;
            private bool toolableOnly;
            private MembersInjectorImpl<T> outer;

            public Context(MembersInjectorImpl<T> outer, T instance, Errors errors, bool toolableOnly)
            {
                this.instance = instance;
                this.errors = errors;
                this.toolableOnly = toolableOnly;
                this.outer = outer;
            }
            public AsyncVoid Call(InternalContext context)
            {
                this.outer.InjectMembers(instance, errors, context, toolableOnly);
                return null;
            }
        }

        internal void InjectAndNotify(T instance, Errors errors, bool toolableOnly)
        {
            if (instance == null)
            {
                return;
            }

            this.injector.CallInContext(new Context(this, instance, errors, toolableOnly));

            // TODO: We *could* notify listeners too here,
            // but it's not clear if we want to.  There's no way to know
            // if a MembersInjector from the usersMemberInjector list wants
            // toolable injections, so do we really want to notify
            // about injection?  (We could take a strategy of only notifying
            // if atleast one InjectionPoint was toolable, in which case
            // the above callInContext could return 'true' if it injected
            // anything.)
            if (!toolableOnly)
            {
                NotifyListeners(instance, errors);
            }
        }

        internal void NotifyListeners(T instance, Errors errors)
        {
            int numErrorsBefore = errors.Size;
            foreach (InjectionListener injectionListener in injectionListeners)
            {
                try
                {
                    injectionListener.AfterInjection(instance);
                }
                catch (Exception e)
                {
                    errors.ErrorNotifyingInjectionListener<T>((InjectionListener<T>)injectionListener, typeof(T), e);
                }
            }
            errors.ThrowIfNewErrors(numErrorsBefore);
        }

        internal void InjectMembers(T t, Errors errors, InternalContext context, bool toolableOnly)
        {
            // optimization: use manual for/each to save allocating an iterator here
            for (int i = 0, size = memberInjectors.Count; i < size; i++)
            {
                SingleMemberInjector injector = memberInjectors[i];
                if (!toolableOnly || injector.GetInjectionPoint().IsToolable)
                {
                    injector.Inject(errors, context, t);
                }
            }

            // TODO: There's no way to know if a user's MembersInjector wants toolable injections.
            if (!toolableOnly)
            {
                // optimization: use manual for/each to save allocating an iterator here
                for (int i = 0, size = userMembersInjectors.Count; i < size; i++)
                {
                    IMembersInjector userMembersInjector = userMembersInjectors[i];
                    try
                    {
                        userMembersInjector.InjectMembers(t);
                    }
                    catch (Exception e)
                    {
                        errors.ErrorInUserInjector<T>((IMembersInjector<T>)userMembersInjector, typeof(T), e);
                    }
                }
            }
        }

        public override string ToString()
        {
            return "MembersInjector<" + typeof(T) + ">";
        }

        public ImmutableSet<InjectionPoint> GetInjectionPoints()
        {
            ImmutableSet<InjectionPoint>.Builder<InjectionPoint> builder = ImmutableSet<InjectionPoint>.GetBuilder();
            foreach (SingleMemberInjector memberInjector in memberInjectors)
            {
                builder.Add(memberInjector.GetInjectionPoint());
            }
            return builder.Build();
        }

        /*if[AOP]*/
        public ImmutableList<MethodAspect> AddedAspects
        {
            get
            {
                return addedAspects;
            }
        }
        /*end[AOP]*/
    }
}
